// OSR b jvcs
package org.jvcs.common;

import org.jvcs.common.*;

import java.io.*;
import java.util.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.regex.*;

public class Utils {


	// GREP 
	private static Charset charset = Charset.forName("ISO-8859-15");
	private static CharsetDecoder decoder = charset.newDecoder();
	private static Pattern linePattern = Pattern.compile(".*\r?\n");
	private static Pattern pattern;
	public static void grep(File f) {
		try {
			FileInputStream fis = new FileInputStream(f);
			FileChannel fc = fis.getChannel();
			int sz = (int)fc.size();
			MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
			CharBuffer cb = decoder.decode(bb);
			grep(f, cb);
			fc.close();
		} catch (Exception e1) {
			osr.ee("Utils grep: "+e1.toString());
		}
    }
	public static void grep(File f, CharBuffer cb) {
        Matcher lm = linePattern.matcher(cb);   
        Matcher pm = null;                      
        int lines = 0;
		int foundLines = 0;
        while (lm.find()) {
            lines++;
            CharSequence cs = lm.group();       
            if (pm == null)
                pm = pattern.matcher(cs);
            else
                pm.reset(cs);
            if (pm.find()) {
				String dfs = stdPath(f.toString());
				if (CmdLine.contextFlag && (CmdLine.contextValue != null)) {
					try {
						FileInputStream fs1= new FileInputStream(f.toString());
						BufferedReader br1 = new BufferedReader(new InputStreamReader(fs1));
						int no_of_lines = Integer.parseInt(CmdLine.contextValue);
						int max = lines+no_of_lines;
						int min = lines-no_of_lines;
						if (min < 0) {min = 0;}
						//osr.pl("============> context => "+min+", "+lines+", "+max+", "+no_of_lines);
						List<String> mlines = new ArrayList<String>();
						String linex = null;
						String marker = "";
						int flcnt = 1;
						while ((linex = br1.readLine()) != null) {
							
							if ( (flcnt >= min) && (flcnt <= max)) {
								if ((flcnt >= min) && (flcnt < lines)) marker = "-";
								if (flcnt == lines) marker = "*";
								if ((flcnt > lines) && (flcnt <= max)) marker = "+";
								
								String pline = dfs+":"+flcnt+":"+marker+":"+linex;
								osr.pl(pline);
							} 
							flcnt++;
						}
	
						br1.close();
						fs1.close();
						//mlines.toArray(new String[mlines.size()]);

					
					} catch (Exception e1) {
						osr.pl("Warning: Unable to get the line for context of grep");
					}
				
				} else {
					osr.p(dfs + ":" + lines + ":" + cs);
				}
				foundLines++;
			}
            if (lm.end() == cb.limit()) {
				
                break;
			}
        }
		if (foundLines > 0)
			osr.pl("====");
	
    }
	public static void compile(String pat, boolean caseReq) {
		
        try {
			if (!caseReq) {
				pattern = Pattern.compile(pat, Pattern.CASE_INSENSITIVE);				
			} else {
				pattern = Pattern.compile(pat);		
			}
           
        } catch (PatternSyntaxException x) {
           osr.ee(x.getMessage());
        }	
    }
	
	
	
	// END GREP


	public static void touchFile (String filePath) {
		try {
			File fId = new File(filePath);
			if (!fId.exists()) {fId.createNewFile();}
		} catch (Exception e) {
			errExit("Unable to create : "+filePath);
		}
	}
	public static void deleteFile (String filePath) {
		try {
			File fId = new File(filePath);
			if (fId.exists()) {fId.delete();}
		} catch (Exception e) {
			errExit("Unable to delete : "+filePath);
		}
	}
	public static void check1(String mesg) {
		System.out.println(mesg);
	}
	
	public static void display(String mesg) {
		System.out.println(mesg);
	}
	public static void errExit(String mesg) {
		System.out.println("Error: "+mesg);
		System.exit(-1);
	}
	public static String stdPath(String inPath) {
		return inPath.replaceAll("\\\\", "/");
	}
	public static boolean mkdir(String inPath) {
		boolean o = false;
		try {
			File inP = new File(inPath);
			if (!inP.exists()) {inP.mkdir();}
		} catch (Exception e) {
			errExit("mkdir : "+inPath+" : "+e.toString());
		}
		return o;
	}
	public static boolean chkDirExists(String inPath) {
		boolean o = false;
		try {
			File inP = new File(inPath);
			if (!inP.isFile() && inP.exists()) {return true;}
		} catch (Exception e) {
			errExit("Dir Exists Check: "+e.toString());
		}
		return o;
	}
	public static boolean chkFileExists(String inPath) {
		boolean o = false;
		try {
			File inP = new File(inPath);
			if (inP.isFile() && inP.exists()) {return true;}
		} catch (Exception e) {
			errExit("File Exists Check: "+e.toString());
		}
		return o;
	}
	
	public static long getFileSize(String filename) {
		File file = new File(filename);
		if (file.exists() && file.isFile()) {
			return file.length();
		} else {
			return 0;
		}
	}
	
	public static String getMD5SUM(String filename) {
		File file = new File(filename);
		
		if (file.exists() && file.isFile()) {
			return MD5SumUtils.get(file.toString());
		} else {
			return "";
		}
	}
	
	public static void checkLockExit(String lf, String mesg) {
		File f = new File(lf);
		if (f.exists() && f.isFile()) {
			errExit(mesg + "Lock exists, please retry again. Exiting now.");
		}
	}
	
	// COLLECTIVE: DIR LISTING and STORE(main)
	public static void store (String inputDir, String rProject, String hubDir) {
		List<File> files = null;
		List<String> index = null;
		StringBuffer osrSB = null;
		String md5sum  = null;
		String ifn = null;
		String tfn = null;
		StringBuffer osrTable = null;
		int writeIndexFlag = 0;
		String actualIndexFile = null;
		String projectActualIndexFile = null;
		String actualTableFile = null;
		String relPath = null;
		String refPath = null;
		String hubFilesDir = null;
		Map<String, String> fileMap = new HashMap<String,String>();
		Map<String, String> projectFileMap = new HashMap<String,String>();
		Map.Entry me = null;
		
		int noOfFolders = 0;
		int noOfFiles = 0;
		int totalNoOfFilesFolders = 0;
		int noOfDuplicateFiles = 0;
		
		String lockFile = null;
		
		String projectPtr = null;
			
		try {
			osrSB = new StringBuffer();
			osrTable = new StringBuffer();
			refPath = stdPath(inputDir);
			File startingDirectory= new File(refPath);
			
			files = getFileListing(startingDirectory);
			index = new ArrayList<String>();
			
			hubFilesDir = hubDir + "/" + Global.cHubFilesDir;
			mkdir(hubFilesDir);
			
			projectPtr = hubDir + "/" + rProject  +"/"+ Global.fDataDir + "/" +   Global.fMainline + "/" + Global.fBaseVer ;
			projectPtr = stdPath(projectPtr);
			display("Project Dir: "+projectPtr);
			mkdir(projectPtr);
			actualIndexFile = hubFilesDir +"/"+  Global.indexFileName;
			projectActualIndexFile = stdPath(hubDir +"/"+ rProject  +"/"+ Global.fFilesDir + "/"+  Global.indexFileName);
			actualTableFile  = projectPtr + "/"+ Global.tableFileName;
			
			lockFile = stdPath(hubDir + "/" + rProject  +"/"+ Global.cProjectInitLockFile);
			checkLockExit(lockFile, "Project is being initialized by another user, Init ");
			touchFile(lockFile);
			
			touchFile(actualIndexFile);
			touchFile(projectActualIndexFile);
			touchFile(actualTableFile);			
			
			fileMap = readMD5IndexFile(actualIndexFile);
			projectFileMap = readMD5IndexFile(projectActualIndexFile);
			//print out all file names, in the the order of File.compareTo()
			for(File file : files ){
				String filePath = stdPath(file.toString());
				long size = getFileSize(filePath);
				
				String itemType = "DIR";
				if (!file.isFile()) {
					itemType = "DIR";
					md5sum = "";
					System.out.print(" d ");
					noOfFolders++;
				} else {
				
					md5sum = getMD5SUM(file.toString());					
					
					itemType = "FILE";
					filePath = stdPath(filePath);
					hubDir = stdPath(hubDir);
					noOfFiles++;
					//display("Saving ... "+filePath+" ===> "+filesDir);
					if (fileMap.containsKey(md5sum) ) {
						System.out.print(" * ");
						noOfDuplicateFiles++;
					} else {			
						System.out.print(" + ");
						fileMap.put(md5sum, "File");
						projectFileMap.put(md5sum, "File");
						writeIndexFlag++;
						copyFile(filePath, md5sum, hubFilesDir);
					}
				}
				
				relPath = stdPath(filePath);
				if (!refPath.equals(".")) {
					relPath = relPath.replace(refPath, "");
					relPath = "."+relPath;
				}
				
				
				String t1 =  itemType+"|"+md5sum  + "|" + Global.fMainline  + "|" + Global.fBaseVer + "|" + size +"|"+relPath;
				osrTable.append(t1+"\n");
				String disp = String.format("%4s",itemType) + " " + String.format("%32s",md5sum)+ " "+ filePath;
				index.add(t1);				
				
				display(disp);
				
				
				
			}	
			
			
			// Writing the file contents of Index file
			if (writeIndexFlag > 0) {
				Set set = fileMap.entrySet(); 
				Iterator i = set.iterator(); 
				while (i.hasNext()) {
					me = (Map.Entry)i.next(); 
					osrSB.append(me.getKey()+"\n");
				}
				lockWriteFile(actualIndexFile, osrSB.toString());
			}
			// Writing the file contents of Project Index file
			if (writeIndexFlag > 0) {
				osrSB = new StringBuffer();
				Set set = projectFileMap.entrySet(); 
				Iterator i = set.iterator(); 
				while (i.hasNext()) {
					me = (Map.Entry)i.next(); 
					osrSB.append(me.getKey()+"\n");
				}
				lockWriteFile(projectActualIndexFile, osrSB.toString());
			}
			
			// Write the File/Dir Table
			lockWriteFile(actualTableFile, osrTable.toString());
				
			// Release the lock	
			display(lockFile);
			deleteFile(lockFile);
			
		} catch (Exception e) {
			errExit("Error: getDirDetails "+e.toString());
		}
		totalNoOfFilesFolders = noOfFiles + noOfFolders;
		
		StringBuffer sb1 = new StringBuffer();
		sb1.append("\n============================================================\nSummary:\n\n");
		sb1.append(String.format("%31s", "No. of Files: "));
		sb1.append(String.format("%20s", ""+noOfFiles+"\n"));
		sb1.append(String.format("%31s", "No. of Folders: "));
		sb1.append(String.format("%20s", ""+noOfFolders+"\n"));
		sb1.append(String.format("%31s", "No. of Duplicate Files: "));
		sb1.append(String.format("%20s", ""+noOfDuplicateFiles+"\n"));
		sb1.append("\n============================================================\n");
		sb1.append(String.format("%31s", "Total No. of Items: "));		
		sb1.append(String.format("%20s", ""+totalNoOfFilesFolders+"\n"));
		System.out.println(sb1.toString());
		
	}
	
	// implode an Array 
	public static String implode(String [] arr, String delim) {
		StringBuffer sb1 = new StringBuffer();
		for (String e1 : arr) {
			sb1.append(e1 + delim);
		}
		return sb1.toString();
	}
	
	// Join two arrays
	public static String [] joinArrays (String [] arr1, String [] arr2) {
		String [] ret = new String[arr1.length+arr2.length];
		int counter = 0;
		int acounter = 0;
		for (String e1 : arr1){
			ret[counter++] = arr1[acounter++];
		}
		acounter = 0;
		for (String e2 : arr2){
			ret[counter++] = arr2[acounter++];
		}
		return ret;
	}
	
	// Check in Array
	public static boolean arrayElementExists (String [] strArray, String searchItem) {
		boolean found = false;
		for (String eachItem: strArray) {
			if (searchItem.equals(eachItem)) {
				return true;
			}
		}	
		return found;
	}
	// DIR LISTING (main) (unused at this moment) for sample derived store method out of this
	public static void getDirDetails (String dirPath) {
		List<File> files = null;
		List<String> index = null;
		try {
			File startingDirectory= new File(dirPath);
			files = getFileListing(startingDirectory);
			index = new ArrayList<String>();
			//print out all file names, in the the order of File.compareTo()
			for(File file : files ){
				String filePath = stdPath(file.toString());
				long size = getFileSize(filePath);
				String md5sum = getMD5SUM(file.toString());
				String itemType = "DIR";
				if (!file.isFile()) {itemType = "DIR";} else {
					itemType = "FILE";
				}
				String t1 = filePath +"||"+ size+"||"+md5sum+"||"+itemType;
				index.add(t1);
				System.out.println(t1);
				
			}	
		} catch (Exception e) {
			errExit("Error: getDirDetails "+e.toString());
		}
		int noOfFiles = files.size();
		System.out.println("No. of Files: "+noOfFiles);
		
	}
	
/**
  * Recursively walk a directory tree and return a List of all
  * Files found; the List is sorted using File.compareTo().
  *
  * @param aStartingDir is a valid directory, which can be read.
  */
  public static List<File> getFileListing(File aStartingDir) throws FileNotFoundException {
    validateDirectory(aStartingDir);
    List<File> result = getFileListingNoSort(aStartingDir);
    Collections.sort(result);
    return result;
  }

  // PRIVATE //
  public static List<File> getFileListingNoSort(File aStartingDir) throws FileNotFoundException {
    List<File> result = new ArrayList<File>();
    File[] filesAndDirs = aStartingDir.listFiles();
    List<File> filesDirs = Arrays.asList(filesAndDirs);
    for(File file : filesDirs) {
      result.add(file); //always add, even if directory
      if ( ! file.isFile() ) {
        //must be a directory
        //recursive call!
        List<File> deeperList = getFileListingNoSort(file);
        result.addAll(deeperList);
      }
    }
    return result;
  }

  /**
  * Directory is valid if it exists, does not represent a file, and can be read.
  */
  public static void validateDirectory (File aDirectory) throws FileNotFoundException {
    if (aDirectory == null) {
      throw new IllegalArgumentException("Directory should not be null.");
    }
    if (!aDirectory.exists()) {
      throw new FileNotFoundException("Directory does not exist: " + aDirectory);
    }
    if (!aDirectory.isDirectory()) {
      throw new IllegalArgumentException("Is not a directory: " + aDirectory);
    }
    if (!aDirectory.canRead()) {
      throw new IllegalArgumentException("Directory cannot be read: " + aDirectory);
    }
  }

	
	// COPY dir
	public static void copyDirectory(File sourceLocation , File targetLocation)
    throws IOException {
        
        if (sourceLocation.isDirectory()) {
            if (!targetLocation.exists()) {
                targetLocation.mkdir();
            }
            
            String[] children = sourceLocation.list();
            for (int i=0; i<children.length; i++) {
                copyDirectory(new File(sourceLocation, children[i]),
                        new File(targetLocation, children[i]));
            }
        } else {
            
            InputStream in = new FileInputStream(sourceLocation);
            OutputStream out = new FileOutputStream(targetLocation);
            
            // Copy the bits from instream to outstream
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }
    }
	public static void copyFile(String a, String md5, String b) {
		InputStream in = null;
		OutputStream out = null;
		File src = null;
		File dst = null;
		
		try {
			src = new File(a);
			String parent = src.getParent();
			String name = src.getName();
			dst = new File(b+"/"+md5);
			in = new FileInputStream(src);
			out = new FileOutputStream(dst);

			// Transfer bytes from in to out
			byte[] buf = new byte[1024];
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}
			in.close();
			out.close();
		} catch (Exception e) {
			errExit("Unable to copyFile: "+src + "\n"+e.toString());
		}
	}
	// SUB UTILS 
	public static Map<String,String> readMD5IndexFile (String filePath) {
		File file = null;
		FileInputStream fis = null;
		BufferedInputStream bis = null;
		DataInputStream dis = null;
		String line = null;
		Map<String,String> m = new HashMap<String,String>();
		String mType = "File";
		int lineCounter = 0;
		try {
			BufferedReader in = new BufferedReader(new FileReader(filePath));
			while ((line = in.readLine()) != null) {
				m.put(line, mType);
			}
			in.close();
			
		} catch (Exception e) {
			errExit("Error in readMD5IndexFile: "+e.toString());
		}
		return m;
	}
/*
	// SUB UTILS 
	public static String [] readMD5IndexFile (String filePath) {
		File file = null;
		FileInputStream fis = null;
		BufferedInputStream bis = null;
		DataInputStream dis = null;
		String line = null;
		String [] rLines = {};
		List<String> tLines = new ArrayList<String>();
		int lineCounter = 0;
		try {
			BufferedReader in = new BufferedReader(new FileReader(filePath));
			while ((line = in.readLine()) != null) {
				tLines.add(line);
			}
			in.close();

			rLines = new String[tLines.size()];
			tLines.toArray(rLines);
		} catch (Exception e) {
			errExit("Error in readMD5IndexFile: "+e.toString());
		}
		return rLines;
	}
*/	
	// WRITE FILE
	
	public static void lockWriteFile(String fileName, String contents) {

	try {
		File file = new File(fileName);
		FileChannel channel = new FileOutputStream(file, false).getChannel();
		String phrase = contents;
		FileLock lock = channel.tryLock();
		
		//System.out.println("LOCK :"+lock);
		if (lock == null) {
			//System.out.print("Waiting for lock ");
			int counter = 5; // wait for 5 secs
			while (counter !=0 ) {
					Thread.sleep(1000);
					lock = channel.tryLock();
					if (lock != null) { break;}
					System.out.print(".");
					counter--;
			}
			if (lock == null) 
				errExit("Unable to attain lock, process still occupying");
		} 
			
			if (lock != null) {
				ByteBuffer buf = null;
				buf = ByteBuffer.wrap(phrase.getBytes());
				channel.write(buf);
			} 
		

		// Release the lock
		lock.release();

		// Close the file
		channel.close();
	} catch (Exception e) {
		errExit("Error lockWriteFile: "+e.toString());
	}

	}
	
}

// End JVCS
